package rr.core.display
{
	import flash.display.Sprite;
	import flash.display.Loader;
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.DisplayObject;
	import flash.display.IBitmapDrawable;
	
	import flash.net.URLRequest;
	
	import flash.geom.Point;
	import flash.geom.Matrix;
	
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	
	/**
	 * @author Raeef
	 */
	public class BitmapCore extends Sprite implements IBitmapCore
	{
		private var _bitmap		:Bitmap	 = null;
		
		private var _loader		:Loader	 = null;
		
		private var _transparent:Boolean = true;
		
		private var _fillColor	:uint	 = 0;
		
		private var _width		:Number	 = 0;
		private var _height		:Number	 = 0;
		
		private var _align		:String	 = null;
		private var _pivot		:String	 = null;
		private var _adjustType	:String	 = null;
		
		/**
		 * Constructor
		 * 
		 * @param	disObj: It defines a 'DisplayObject' to draw into the 'BitmapData' or keep it as 'null' to load external image by 'load' method.
		 * @param	width: By defining this param you will constrain the disObj param width to it or keep it NaN to let it free.
		 * @param	height: By defining this param you will constrain the disObj param height to it or keep it NaN to let it free.
		 * @param	adjustType: If you defined 'width' or 'height' then you can select the behavior you want to resize the image by: BitmapAdjustType class constants.
		 * @param	align: When you select 'BitmapAdjustType.FITTING' for adjustType param then sometimes you will get free spaces so you can adjust this param to align the bitmap in that spaces or when you select 'BitmapAdjustType.CLIPPING' then you can ajust the clipping align.
		 * @param	pivot: Defines the registration point.
		 * @param	transparent: When you select 'BitmapAdjustType.FITTING' for adjustType param then sometimes you will get free spaces so you can adjust this param to show or hide those spaces.
		 * @param	fillColor: When you select 'BitmapAdjustType.FITTING' for adjustType param then sometimes you will get free spaces so you can adjust this param to fill those spaces with specific color.
		 */
		public function BitmapCore(disObj:DisplayObject = null, width:Number = NaN, height:Number = NaN, adjustType:String = 'clipping', align:String = 'top_left', pivot:String = "top_left", transparent:Boolean = true, fillColor:uint = 0):void
		{
			this._width		  = width;
			this._height	  = height;
			this._adjustType  = adjustType;
			this._align		  = align;
			this._pivot		  = pivot;
			this._transparent = transparent;
			
			if (disObj) this.init(disObj);
		}
		
		/**
		 * BitmapData object.
		 */
		public function get bitmapData():BitmapData
		{
			return this._bitmap ? this._bitmap.bitmapData : null;
		}
		
		/**
		 * The original Bitmap Object.
		 */
		public function get bitmapObj():Bitmap
		{
			return this._bitmap;
		}
		
		/**
		 * Draw 'DisplayObject' into the 'BitmapData'.
		 * Note: if you didn't defined the disObj param in the constructor or you didn't use the 'load' method then you can use this method for one time.
		 * 
		 * @param	disObj: It defines a 'DisplayObject' to draw into the 'BitmapData'.
		 */
		public function init(disObj:DisplayObject):void
		{
			if (!this._bitmap)
			{
				this._width			  = isNaN(this._width)  ? disObj.width  : this._width;
				this._height		  = isNaN(this._height) ? disObj.height : this._height;
				
				var matrix	  :Matrix = null;
				var alignPoint:Point  = null;
				
				switch(this._adjustType)
				{
					case BitmapAdjustType.STRETCHING:
						
						this._bitmap		= createBitmap(disObj, disObj.width, disObj.height, matrix, this._transparent, this._fillColor);
						this._bitmap.width  = this._width;
						this._bitmap.height = this._height;
						
					break;
					
					case BitmapAdjustType.FITTING:
						
						var spaceRatio :Number	= this._width / this._height;
						var disObjRatio:Number	= disObj.width / disObj.height
						
						var tempWidth  :Number	= this._width;
						var tempHeight :Number	= this._height;
						
						if (this._width < disObj.width || this._height < disObj.height)
						{
							if (spaceRatio < disObjRatio)
							{
								tempWidth  = disObj.width;
								tempHeight = disObj.width / spaceRatio;
							}
							else
							{
								tempWidth  = disObj.height * spaceRatio;
								tempHeight = disObj.height;
							}
						}
						
						alignPoint	 = Align.getDisObjAlignPoint(disObj.width, disObj.height, tempWidth, tempHeight, this._align);
						
						matrix		 = new Matrix(1, 0, 0, 1, alignPoint.x, alignPoint.y);
						
						this._bitmap = createBitmap(disObj, tempWidth, tempHeight, matrix, this._transparent, this._fillColor);
						
						if (this._bitmap.width > this._width)
						{
							this._bitmap.width  = this._width;
							this._bitmap.scaleY = this._bitmap.scaleX;
						}
						
						if (this._bitmap.height > this._height)
						{
							this._bitmap.height = this._height;
							this._bitmap.scaleX = this._bitmap.scaleY;
						}
						
					break;
					
					default:
						
						alignPoint	 = Align.getDisObjAlignPoint(disObj.width, disObj.height, this._width, this._height, this._align);
						
						matrix		 = new Matrix(1, 0, 0, 1, alignPoint.x, alignPoint.y);
						
						this._bitmap = createBitmap(disObj, this._width, this._height, matrix, this._transparent, this._fillColor);
						
					break;
				}
				
				Align.setDisObjPivot(this._bitmap, this._pivot);
				
				this.addChild(this._bitmap);
				
				this.dispatchEvent(new Event(Event.INIT));
			}
			else trace('you can init "' + this.toString() + '" just for one time.');
		}
		
		/**
		 * To load external image if you didn't define the disObj param or used 'init' method.
		 * 
		 * @param	url: A url for an external image.
		 */
		public function load(url:String):void
		{
			if (!this._bitmap && !this._loader)
			{
				this._loader = new Loader();
				this._loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, this.ioErrorHandler);
				this._loader.contentLoaderInfo.addEventListener(Event.COMPLETE		 , this.loadCompleteHandler);
				this._loader.load(new URLRequest(url));
			}
			else trace('you can use load function in "' + this.toString() + '" just for one time.');
		}
		
		public function kill():void
		{
			if (this._loader)
			{
				this._loader.close();
				this._loader.contentLoaderInfo.removeEventListener(IOErrorEvent.IO_ERROR, this.ioErrorHandler);
				this._loader.contentLoaderInfo.removeEventListener(Event.COMPLETE		, this.loadCompleteHandler);
				this._loader = null;
			}
			
			if (this._bitmap) this._bitmap.bitmapData.dispose();
		}
		
		private function ioErrorHandler(event:IOErrorEvent):void
		{
			event.target.removeEventListener(IOErrorEvent.IO_ERROR, this.ioErrorHandler);
			event.target.removeEventListener(Event.COMPLETE		  , this.loadCompleteHandler);
			
			this._loader = null;
			
			this.dispatchEvent(new IOErrorEvent(IOErrorEvent.IO_ERROR, false, false, event.text, event.errorID));
		}
		
		private function loadCompleteHandler(event:Event):void
		{
			event.target.removeEventListener(IOErrorEvent.IO_ERROR, this.ioErrorHandler);
			event.target.removeEventListener(Event.COMPLETE		  , this.loadCompleteHandler);
			
			this.init(this._loader);
			
			this._loader.unload();
			this._loader = null;
		}
		
		/**
		 * Static method to create a classic 'Bitmap' object.
		 * 
		 * @param	drawableObject: A drawable object to draw into the 'BitmapData'.
		 * @param	width: Defines the 'BitmapData' width.
		 * @param	height: Defines the 'BitmapData' height.
		 * @param	matrix: Defines the 'BitmapData' matrix.
		 * @param	transparent: Defines the 'BitmapData' transparent.
		 * @param	fillColor: Defines the 'BitmapData' fillColor.
		 * @return	'Bitmap' object.
		 */
		public static function createBitmap(drawableObject:IBitmapDrawable, width:uint, height:uint, matrix:Matrix = null, transparent:Boolean = true, fillColor:uint = 0):Bitmap
		{
			var bitmapData:BitmapData = new BitmapData(width, height, transparent, fillColor);
				bitmapData.draw(drawableObject, matrix, null, null, null, true);
			
			var bitmap:Bitmap = new Bitmap(bitmapData, "auto", true);
			
			return bitmap;
		}
	}
}